<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../../resources/js-test.js"></script>
</head>
<body>
<script>
description("Tests RTCPeerConnection.generateCertificate RSA/ECDSA.");

// Constants
var minuteInMs = 60 * 1000;
var dayInMs = 24 * 60 * minuteInMs;

// Signature of the last generateCertificate call.
var generateCallString = null;
// Global variables so that the "should..." methods can evaluate them.
var certRSA = null;
var certECDSA = null;
var certExpiresNegativeOneDay = null;
var certExpiresZero = null;
var certExpiresPositiveOneDay = null;
var fingerprints = null;

// 1: RSA-2048 using public exponent = 65537.
function generate1RSA()
{
    generateCallString = 'generateCertificate({ name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" })';
    RTCPeerConnection.generateCertificate({ name: "RSASSA-PKCS1-v1_5", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" })
        .then(generate1RSASuccessful, generate1RSAFailed);
}
function generate1RSASuccessful(certificate)
{
    certRSA = certificate;
    testPassed(generateCallString);
    certificateSanityCheck('certRSA');
    generate2ECDSA();
}
function generate1RSAFailed()
{
    testFailed(generateCallString);
    generate2ECDSA();
}

// 2: ECDSA using NIST P-256.
function generate2ECDSA()
{
    generateCallString = 'generateCertificate({ name: "ECDSA", namedCurve: "P-256" })';
    RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256" })
        .then(generate2ECDSASuccessful, generate2ECDSAFailed);
}
function generate2ECDSASuccessful(certificate)
{
    certECDSA = certificate;
    testPassed(generateCallString);
    certificateSanityCheck('certECDSA');
    generate3ExpiresNegativeOneDay();
}
function generate2ECDSAFailed()
{
    testFailed(generateCallString);
    generate3ExpiresNegativeOneDay();
}

// 3-5: Verify that the |expires| attribute works (generate ECDSA because its faster).
function generate3ExpiresNegativeOneDay()
{
    generateCallString = 'generateCertificate({ name: "ECDSA", namedCurve: "P-256", expires:-dayInMs })';
    RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256", expires:-dayInMs })
        .then(function(certificate) { certExpiresNegativeOneDay = certificate; generate4ExpiresZero(); }, generate4ExpiresZero);
}
function generate4ExpiresZero()
{
    generateCallString = 'generateCertificate({ name: "ECDSA", namedCurve: "P-256", expires:0 })';
    RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256", expires:0 })
        .then(function(certificate) { certExpiresZero = certificate; generate5ExpiresPositiveOneDay(); }, generate5ExpiresPositiveOneDay);
}
function generate5ExpiresPositiveOneDay()
{
    generateCallString = 'generateCertificate({ name: "ECDSA", namedCurve: "P-256", expires:dayInMs })';  // +1 day
    RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256", expires:dayInMs })
        .then(function(certificate) { certExpiresPositiveOneDay = certificate; generate3to5ExpiresResolved(); }, generate3to5ExpiresResolved);
}
function generate3to5ExpiresResolved()
{
    // A negative |expires| value is not a DOMTimeStamp, it should be ignored and
    // generate a certificate that has not expired.
    certificateSanityCheck('certExpiresNegativeOneDay');

    // Check that the zero expiration certificate was generated but has expired.
    shouldBeNonNull('certExpiresZero');
    shouldBeTrue('new Date().getTime() >= certExpiresZero.expires');

    // Check that the +1 day expiration certificate expires in approximately 1 day (+/- 1 minute).
    shouldBeNonNull('certExpiresPositiveOneDay');
    shouldBeTrue('Math.abs(certExpiresPositiveOneDay.expires - (new Date().getTime() + dayInMs)) <= minuteInMs');

    finishJSTest();
}

// Helper functions.
function certificateSanityCheck(certVariableName)
{
    shouldBeNonNull(certVariableName);
    shouldBeTrue('new Date().getTime() < ' + certVariableName + '.expires');
    shouldBeTrue(certVariableName + '.getFingerprints().length == 1');
    shouldBeTrue(certVariableName + '.getFingerprints()[0].algorithm === "sha-256"');
    shouldBeTrue('isFingerprintValue(' + certVariableName + '.getFingerprints()[0].value)');
    // Fingerprints are dictionaries, a copy should be returned each time.
    shouldBeTrue(certVariableName + '.getFingerprints()[0] != ' + certVariableName + '.getFingerprints()[0]');
}
function isFingerprintValue(fingerprint) {
    // /                Begin regex
    //   ^              Start of line
    //   (?:            Non-capturing parenthesis
    //     [a-z0-9]{2}  2 lowercase alphanumeric characters
    //     \:           The : character
    //   )+             End of parenthesis, match 1 or more times
    //   [a-z0-9]{2}    2 lowercase alphanumeric characters
    //   $              End of line
    // /                End of regex
    return /^(?:[a-z0-9]{2}\:)+[a-z0-9]{2}$/.test(fingerprint);
}

// Run each generate test sequentially. The ith generate method will make sure
// the (i+1)st generate method is executed when its promise's callbacks are
// invoked. generate2ECDSA's callback methods mark the end of the async tests.
generate1RSA();

window.jsTestIsAsync = true;
window.successfullyParsed = true;
</script>
</body>
</html>
